/*globals $, jQuery, document, window, location, wijmoASPNetParseOptions*/
/*
 *
 * Wijmo Library 2.3.4
 * http://wijmo.com/
 *
 * Copyright(c) GrapeCity, Inc.  All rights reserved.
 * 
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * licensing@wijmo.com
 * http://www.wijmo.com/license
 *
 * * Wijmo Tabs widget.
 *
 * Depends:
 *	jquery-1.4.2.js
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 *	jquery.ui.position.js
 *	jquery.effects.core.js	
 *	jquery.cookie.js
 *  jquery.wijmo.wijsuperpanel.js
 *	jquery.wijmo.wijutil.js
 */




(function ($) {
	"use strict";

	var tabId = 0,
		listId = 0;

	function getNextTabId() {
		return ++tabId;
	}

	function getNextListId() {
		return ++listId;
	}

	$.widget("wijmo.wijtabs", {
		options: {
			/// <summary>
			/// Determines the tabs' alignment in respect to the content.
			/// Possible values are: 'top', 'bottom', 'left' and 'right'.
			/// Type: String
			/// Default: 'top'
			/// Code Example:  
			/// $('.selector').wijtabs({alignment: 'top'});
			/// </summary>
			alignment: 'top',
			/// <summary>
			/// Determines whether the tab can be dragged to a new position.
			/// Type: Boolean
			/// Default: false
			/// Code Example:  
			/// $('.selector').wijtabs({sortable: false});
			/// </summary>
			sortable: false,
			/// <summary>
			/// Determines whether to wrap to the next line or scrolling 
			///		is enabled when the tabs exceed the specified width
			/// Type: Boolean
			/// Default: false
			/// Code Example:  
			/// $('.selector').wijtabs({scrollable: false});
			/// </summary>
			scrollable: false,
			/// <summary>
			/// Additional Ajax options to consider when loading tab content (see $.ajax).
			/// Type: object 
			/// Default: null 
			/// Code Example:  
			/// $( ".selector" ).wijtabs({ ajaxOptions: { async: false } });
			/// </summary>
			ajaxOptions: null,
			/// <summary>
			/// Whether or not to cache remote tabs content,
			/// e.g. load only once or with every click. 
			/// Cached content is being lazy loaded,
			/// e.g once and only once for the first click. 
			/// Note that to prevent the actual Ajax requests from being cached
			/// by the browser you need to provide an extra cache: 
			/// false flag to ajaxOptions.
			/// Type: Boolean
			/// Default: false
			/// Code Example:  
			/// $('.selector').wijtabs({cache: false});
			/// </summary>
			cache: false,
			/// <summary>
			/// Store the latest selected tab in a cookie. 
			/// The cookie is then used to determine the initially selected tab
			/// if the selected option is not defined. 
			/// Requires cookie plugin. The object needs to have key/value pairs
			/// of the form the cookie plugin expects as options. 
			/// Type: object
			/// Default: null, 
			/// e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
			/// Code Example: 
			/// $('.selector').wijtabs({cookie: { 
			/// expires: 7, path: '/', domain: 'jquery.com', secure: true }});
			/// </summary>
			cookie: null,
			/// <summary>
			/// Determines whether a tab can be collapsed by a user.
			/// When this is set to true, an already selected tab
			/// will be collapsed upon reselection. 
			/// Type: Boolean
			/// Default: false
			/// Code Example: 
			/// $('.selector').wijtabs({collapsible: false});
			/// </summary>
			collapsible: false,
			/// <summary>
			/// This is an animation option for hiding the tabs panel content. 
			/// Type: object
			/// Default: null 
			/// Code Example: 
			/// $('.selector').wijtabs({
			/// hideOption: { blind: true, fade: true, duration: 200}});
			/// </summary>
			hideOption: null, // e.g. { blind: true, fade: true, duration: 200}
			/// <summary>
			/// This is an animation option for showing the tabs panel content. 
			/// Type: object
			/// Default: null 
			/// Code Example: 
			/// $('.selector').wijtabs({
			/// showOption: { blind: true, fade: true, duration: 200}});
			/// </summary>
			showOption: null, // e.g. { blind: true, fade: true, duration: 200}
			/// <summary>
			/// An array containing the position of the tabs (zero-based index)
			/// that should be disabled on initialization.
			/// Type: Array
			/// Default: []
			/// Code Example: 
			/// $( ".selector" ).wijtabs({ disabledIndexes: [1, 2] });
			/// </summary>
			disabledIndexes: [],
			/// <summary>
			/// The type of event to be used for selecting a tab.
			/// </summary>
			event: 'click',
			/// <summary>
			/// If the remote tab, its anchor element that is, has no title attribute
			/// to generate an id from, 
			/// an id/fragment identifier is created from this prefix and a unique id
			/// returned by $.data(el), for example "ui-tabs-54".
			/// Type: String
			/// Default: 'ui-tabs-'
			/// </summary>
			idPrefix: 'ui-tabs-',
			/// <summary>
			/// HTML template from which a new tab panel is created in case of
			/// adding a tab with the add method or 
			/// when creating a panel for a remote tab on the fly.
			/// Type: String
			/// Default: ''
			/// Code Example: 
			/// $('.selector').wijtabs({panelTemplate: '<div></div>'});
			/// </summary>
			panelTemplate: '',
			/// <summary>
			/// The HTML content of this string is shown in a tab title
			/// while remote content is loading. 
			/// Pass in empty string to deactivate that behavior. 
			/// An span element must be present in the A tag of the title,
			/// for the spinner content to be visible.
			/// Type: String
			/// Default: ''
			/// Code Example: 
			/// $('.selector').wijtabs({spinner: 'Connecting…'});
			/// </summary>
			spinner: '',
			/// <summary>
			/// HTML template from which a new tab is created and added. 
			/// The placeholders #{href} and #{label} are replaced with the url
			/// and tab label that are passed as 
			/// arguments to the add method.
			/// Type: String
			/// Default: ''
			/// Code Example:   
			/// $('.selector').wijtabs({
			/// tabTemplate: '<div><a href="#{href}"><span>#{label}</span></a></div>'});
			/// </summary>
			tabTemplate: '',
			/// <summary>
			/// The add event handler. A function called when a tab is added.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ add: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			add: null,
			/// <summary>
			/// The remove event handler. A function called when a tab is removed.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ remove: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			remove: null,
			/// <summary>
			/// The select event handler. A function called when clicking a tab.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ select: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			select: null,
			/// <summary>
			/// The beforeShow event handler. A function called before a tab is shown.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ beforeShow: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			/// <returns type="Boolean">
			/// False if want to cancel the following operations.
			/// </returns>
			beforeShow: null,
			/// <summary>
			/// The show event handler. A function called when a tab is shown.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ show: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			show: null,
			/// <summary>
			/// The load event handler. 
			/// A function called after the content of a remote tab has been loaded.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ load: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			load: null,
			/// <summary>
			/// The disable event handler. A function called when a tab is disabled.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ disable: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			disable: null,
			/// <summary>
			/// The enable event handler. A function called when a tab is enabled.
			/// Default: null.
			/// Type: Function.
			/// Code example: $("#element").wijtabs({ enable: function (e, ui) { } });
			/// </summary>
			///
			/// <param name="e" type="Object">jQuery.Event object.</param>
			/// <param name="ui" type="Object">
			/// The data that contains the related ui elements.
			/// ui.tab: The tab element.
			/// ui.panel: The panel element.
			/// ui.index: The index of the tab.
			/// </param>
			enable: null
		},

		_defaults: {
			panelTemplate: '<div></div>',
			spinner: '<em>Loading&#8230;</em>',
			tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
		},

		_create: function () {
			var self = this,
				o = self.options;
			
			// enable touch support:
			if (window.wijmoApplyWijTouchUtilEvents) {
				$ = window.wijmoApplyWijTouchUtilEvents($);
			}
			
			if (self.element.is(":hidden") && self.element.wijAddVisibilityObserver) {
				self.element.wijAddVisibilityObserver(function () {
					var dataObj = self.element.data("wijtabs");
					var wijmoDataObj = self.element.data("wijmoWijtabs");
					self.destroy();
					self.element.data("wijtabs", dataObj);
					self.element.data("wijmoWijtabs", wijmoDataObj);
					self._tabify(true);
					if (self.element.wijRemoveVisibilityObserver) {
						self.element.wijRemoveVisibilityObserver();
					}
				}, "wijtabs");
			}
			
			self._tabify(true);

			if (o.disabledstate || o.disabled) {
				self.disable();
			}
			$.Widget.prototype._create.apply(self, arguments);
		},

		_setOption: function (key, value) {
			$.Widget.prototype._setOption.apply(this, arguments);

			switch (key) {
			case 'selected':
				if (this.options.collapsible && value === this.options.selected) {
					return;
				}
				this.select(value);
				break;

			case 'alignment':
				this._innerDestroy();
				this._tabify(true);
				break;

			default:
				this._tabify();
				break;
			}
		},

		_initScroller: function () {
			var horz = $.inArray(this._getAlignment(), ['top', 'bottom']) !== -1,
				width = 0;
			if (!horz) {
				return;
			}

			this.lis.each(function () {
				width += $(this).outerWidth(true);
			});

			if (!!this.options.scrollable && this.element.innerWidth() < width) {
				if (this.scrollWrap === undefined) {
					this.list.wrap("<div class='scrollWrap'></div>");
					this.scrollWrap = this.list.parent();
					$.effects.save(this.list, ['width', 'height', 'overflow']);
				}

				this.list.width(width + 2);
				this.scrollWrap.height(this.list.outerHeight(true));
				this.scrollWrap.wijsuperpanel({
					allowResize: false,
					hScroller: {
						scrollMode: 'edge'
					},
					vScroller: {
						scrollBarVisibility: 'hidden'
					}
				});
			} else {
				this._removeScroller();
			}
		},

		_removeScroller: function () {
			if (this.scrollWrap) {
				this.scrollWrap.wijsuperpanel('destroy')
					.replaceWith(this.scrollWrap.contents());
				this.scrollWrap = undefined;
				$.effects.restore(this.list, ['width', 'height', 'overflow']);
			}
		},

		_tabId: function (a) {
			var $a = $(a),
				tabId,
				hrefParams;
			if ($a.data && $a.data("tabid")) {
				return $a.data("tabid");
			}
			if (a.href && a.href.length) {
				hrefParams = this._getURLParameters(a.href);
				if (hrefParams.tabId) {
					tabId = hrefParams.tabId;
					$a.data("tabid", tabId);
					return tabId;
				}
			}
			
			tabId = a.title && a.title.replace(/\s/g, '_')
				.replace(/[^A-Za-z0-9\-_:\.]/g, '') ||
				this.options.idPrefix + getNextTabId();
			
			$a.data("tabid", tabId);
			return tabId;
		},
		
		_getURLParameters: function (url) {
			var params = {},
				parametersString,
				parameters;
			if (url.indexOf('?') > -1) {
				parametersString = url.split('?')[1];
				parameters = parametersString.split('&');
				$.each(parameters, function (i, param) {
					var p = param.split('=');
					if (p.length > 1) {
						params[p[0]] = p[1];
					}
				});
			}
			return params;
		},

		_sanitizeSelector: function (hash) {
			// we need this because an id may contain a ":"
			return hash.replace(/:/g, '\\:');
		},

		_cookie: function () {
			var cookie = this.cookie ||
				(this.cookie = this.options.cookie.name ||
				'ui-tabs-' + getNextListId());
			return $.cookie.apply(null, [cookie].concat($.makeArray(arguments)));
		},

		_ui: function (tab, panel) {
			return {
				tab: tab,
				panel: panel,
				index: this.anchors.index(tab)
			};
		},

		_cleanup: function () {
			// restore all former loading tabs labels
			this.lis.filter('.ui-state-processing').removeClass('ui-state-processing')
				.find('span:data(label.tabs)')
				.each(function () {
					var el = $(this);
					el.html(el.data('label.tabs')).removeData('label.tabs');
				});
		},

		_getAlignment: function (tabs) {
			tabs = tabs === undefined ? true : tabs;
			var align = this.options.alignment || 'top';
			if (tabs) {
				return align;
			}

			switch (align) {
			case 'top':
				align = 'bottom';
				break;

			case 'bottom':
				align = 'top';
				break;

			case 'left':
				align = 'right';
				break;

			case 'right':
				align = 'left';
				break;
			}

			return align;
		},

		_saveLayout: function () {
			var props = ['width', 'height', 'overflow'],
				$hide = this.panels.filter(':not(.ui-tabs-hide)');
			$.effects.save(this.element, props);
			$.effects.save(this.list, props);
			$.effects.save(this.element.find('.wijmo-wijtabs-content'), props);
			this.list.width(this.list.width());

			this.element.data('panel.width', $hide.width());
			this.element.data('panel.outerWidth', $hide.outerWidth(true));
		},

		_restoreLayout: function () {
			var props = ['width', 'height', 'overflow'];
			$.effects.restore(this.element, props);
			$.effects.restore(this.list, props);
			$.effects.restore(this.element.find('.wijmo-wijtabs-content'), props);
		},

		_hideContent: function () {
			var content = this.element.find('.wijmo-wijtabs-content');
			if (content.length) {
				this._saveLayout();
				content.addClass('ui-tabs-hide').attr('aria-hidden', true);
				this.element.width(this.list.outerWidth(true));
			}
		},

		_showContent: function () {
			var content = this.element.find('.wijmo-wijtabs-content');
			if (content.length) {
				this._restoreLayout();
				content.removeClass('ui-tabs-hide').attr('aria-hidden', false);
			}
		},

		_blindPanel: function (panel, mode) {
			var o = this.options,
				content = panel.parent('.wijmo-wijtabs-content'),
				props = ['position', 'top', 'left', 'width'],
				blindOption = mode === 'show' ? o.showOption : o.hideOption,
				wrapper, a, listWidth,
				self = this;
			if (!content.length) {
				return;
			}

			this.list.width(this.list.width());
			$.effects.save(panel, props);
			panel.show(); // Save & Show

			if (mode === 'show') {
				panel.removeClass('ui-tabs-hide').attr('aria-hidden', false); // Show
				panel.width(this.element.data('panel.width'));
			} else {
				panel.width(panel.width());
			}

			// Create Wrapper
			wrapper = $.effects.createWrapper(panel).css({ overflow: 'hidden' });
			if (mode === 'show') {
				// Shift
				wrapper.css($.extend({ width: 0 },
					blindOption.fade ? { opacity: 0} : {}));
			}

			// Animation
			a = $.extend({
				width: mode === 'show' ? this.element.data('panel.outerWidth') : 0
			}, blindOption.fade ? { opacity: mode === 'show' ? 1 : 0} : {});

			listWidth = this.list.outerWidth(true);
			// Animate
			wrapper.animate(a, {
				duration: blindOption.duration,
				step: function () {
					var ww = wrapper.outerWidth(true);
					self.element.width(listWidth + ww);
					content.width(Math.max(0, self.element.innerWidth() - listWidth - 6));
				},
				complete: function () {
					if (mode === 'hide') {
						self.lis
							.removeClass('ui-tabs-selected ui-state-active')
							.attr('aria-selected', false);
						panel.addClass('ui-tabs-hide').attr('aria-hidden', true); // Hide
					} else {
						panel.css('width', '');
					}
					//$.effects.restore(panel, props); 
					$.effects.removeWrapper(panel); // Restore

					if (mode === 'show') {
						self._restoreLayout();
					}

					self._resetStyle(panel);
					panel.dequeue();
					self.element.dequeue("tabs");
				}
			});
		},

		// Reset certain styles left over from animation
		// and prevent IE's ClearType bug...
		_resetStyle: function ($el) {
			$el.css({ display: '' });
			if (!$.support.opacity) {
				$el[0].style.removeAttribute('filter');
			}
		},

		_normalizeBlindOption: function (o) {
			if (o.blind === undefined) {
				o.blind = false;
			}
			if (o.fade === undefined) {
				o.fade = false;
			}
			if (o.duration === undefined) {
				o.duration = 200;
			}
			if (typeof o.duration === 'string') {
				try {
					o.duration = parseInt(o.duration, 10);
				}
				catch (e) {
					o.duration = 200;
				}
			}
		},

		_tabify: function (init) {
			this.list = this.element.children('ol,ul').eq(0);
			this.lis = $('li:has(a)', this.list);
			this.anchors = this.lis.map(function () {
				return $('a', this)[0];
			});
			this.panels = $([]);

			var self = this, o = this.options,
				fragmentId = /^#.+/, // Safari 2 reports '#' for an empty hash
				tabsAlign, panelCorner, content, i, li,
				addState, removeState, showTab, hideTab;
			this.anchors.each(function (i, a) {
				var href = $(a).attr('href') || "",
				// For dynamically created HTML that contains a hash as href IE < 8 
				// expands such href to the full page url with hash and then 
				// misinterprets tab as ajax.
				// Same consideration applies for an added tab with a fragment identifier
				// since a[href=#fragment-identifier] does unexpectedly not match.
				// Thus normalize href attribute...
					hrefBase = href.split('#')[0], baseEl, id, $panel;
				//if (hrefBase && (hrefBase === location.toString().split('#')[0] ||
				if (hrefBase && (hrefBase === location.toString().split('#')[0] ||
					(baseEl = $('base')[0]) && hrefBase === baseEl.href)) {
					href = a.hash;
					a.href = href;
				}

				// inline tab
				if (fragmentId.test(href)) {
					self.panels = self.panels.add(self._sanitizeSelector(href),
						self.element);
				}

				// remote tab
				// prevent loading the page itself if href is just "#"
				else if (href !== '#') {
					$.data(a, 'href.tabs', href); // required for restore on destroy
					$.data(a, 'load.tabs', href.replace(/#.*$/, '')); // mutable data

					id = self._tabId(a);
					a.href = '#' + id;
					$panel = $('#' + id);
					if (!$panel.length) {
						$panel = $(o.panelTemplate ||
							self._defaults.panelTemplate).attr('id', id)
							.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom')
						.insertAfter(self.panels[i - 1] || self.list);
						$panel.data('destroy.tabs', true);
					}
					self.panels = self.panels.add($panel);
				}

				// invalid tab href
				else {
					o.disabledIndexes.push(i);
				}
			});

			tabsAlign = this._getAlignment();
			panelCorner = this._getAlignment(false);

			// initialization from scratch
			if (init) {
				// ARIA
				this.list.attr("role", "tablist");
				this.lis.attr("role", "tab");
				this.panels.attr("role", "tabpanel");

				this.element.addClass('ui-tabs wijmo-wijtabs' +
					' ui-tabs-' + tabsAlign + 
					' ui-widget ui-widget-content ui-corner-all ui-helper-clearfix');
				this.list.addClass('ui-tabs-nav ui-helper-reset ' + 
					'ui-helper-clearfix ui-widget-header ui-corner-all');
				this.lis.addClass('ui-state-default' + ' ui-corner-' + tabsAlign);
				this.panels.addClass('ui-tabs-panel ui-widget-content ui-corner-' + 
					panelCorner);

				// attach necessary classes for styling
				switch (tabsAlign) {
				case 'bottom':
					this.list.appendTo(this.element);
					break;

				case 'left':
					content = $('<div/>').addClass('wijmo-wijtabs-content')
						.appendTo(this.element);
					this.panels.appendTo(content);
					break;

				case 'right':
					content = $('<div/>').addClass('wijmo-wijtabs-content')
						.insertBefore(this.list);
					this.panels.appendTo(content);
					break;

				case 'top':
					this.list.prependTo(this.element);
					break;
				}

				if (o.sortable) {
					this.list.sortable({ axis: (tabsAlign === 'top' || 
						tabsAlign === 'bottom') ? "x" : "y" });
				}

				// Selected tab
				// use "selected" option or try to retrieve:
				// 1. from fragment identifier in url
				// 2. from cookie
				// 3. from selected class attribute on <li>
				if (o.selected === undefined) {
					if (location.hash) {
						this.anchors.each(function (i, a) {
							if (a.hash === location.hash) {
								o.selected = i;
								return false; // break
							}
						});
					}
					if (typeof o.selected !== 'number' && o.cookie) {
						o.selected = parseInt(self._cookie(), 10);
					}
					if (typeof o.selected !== 'number' && 
							this.lis.filter('.ui-tabs-selected').length) {
						o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
					}
					o.selected = o.selected || (this.lis.length ? 0 : -1);
				}
				else if (o.selected === null) {
					// usage of null is deprecated, TODO remove in next release
					o.selected = -1;
				}

				// sanity check - default to first tab...
				o.selected = ((o.selected >= 0 && this.anchors[o.selected]) || 
					o.selected < 0) ? o.selected : 0;

				// Take disabling tabs via class attribute from HTML
				// into account and update option properly.
				// A selected tab cannot become disabled.
				o.disabledIndexes = $.unique(o.disabledIndexes.concat(
					$.map(this.lis.filter('.ui-state-disabled'), function (n, i) {
						return self.lis.index(n); 
					})
				)).sort();

				if ($.inArray(o.selected, o.disabledIndexes) !== -1) {
					o.disabledIndexes.splice($.inArray(o.selected, o.disabledIndexes), 1);
				}

				// highlight selected tab
				this.panels.addClass('ui-tabs-hide').attr('aria-hidden', true);
				this.lis.removeClass('ui-tabs-selected ui-state-active')
					.attr('aria-selected', false);
				// check for length avoids error when initializing empty list
				if (o.selected >= 0 && this.anchors.length) {
					this.panels.eq(o.selected).removeClass('ui-tabs-hide')
						.attr('aria-hidden', false);
					this.lis.eq(o.selected).addClass('ui-tabs-selected ui-state-active')
						.attr('aria-selected', true);

					// seems to be expected behavior that the show callback is fired
					self.element.queue("tabs", function () {
						if (self.element.wijTriggerVisibility) {
							$(self.panels[o.selected]).wijTriggerVisibility();
						}
						self._trigger('show', null, 
							self._ui(self.anchors[o.selected], self.panels[o.selected]));
					});

					this.load(o.selected);
				}

				// clean up to avoid memory leaks in certain versions of IE 6
				$(window).bind('unload', function () {
					if (self.lis) {
						self.lis.add(self.anchors).unbind('.tabs');
					}
					self.lis = self.anchors = self.panels = null;
				});
			} else { // update selected after add/remove
				o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
			}

			// update collapsible
			this.element[o.collapsible ? 'addClass' : 
				'removeClass']('ui-tabs-collapsible');

			// set or update cookie after init and add/remove respectively
			if (o.cookie) {
				this._cookie(o.selected, o.cookie);
			}

			// disable tabs
			for (i = 0; (li = this.lis[i]); i++) {
				$(li)[$.inArray(i, o.disabledIndexes) !== -1 &&
				!$(li).hasClass('ui-tabs-selected') ? 'addClass' : 
					'removeClass']('ui-state-disabled');
				if ($(li).hasClass('ui-state-disabled')) {
					$(li).attr('aria-disabled', true);
				}
			}

			// reset cache if switching from cached to not cached
			if (o.cache === false) {
				this.anchors.removeData('cache.tabs');
			}

			// remove all handlers before, tabify may run on existing tabs
			// after add or option change
			this.lis.add(this.anchors).unbind('.tabs');

			if (!o.disabledState && !o.disabled && o.event !== 'mouseover') {
				addState = function (state, el) {
					if (el.is(':not(.ui-state-disabled)')) {
						el.addClass('ui-state-' + state);
					}
				};
				removeState = function (state, el) {
					el.removeClass('ui-state-' + state);
				};
				this.lis.bind('mouseover.tabs', function () {
					addState('hover', $(this));
				});
				this.lis.bind('mouseout.tabs', function () {
					removeState('hover', $(this));
				});
				this.anchors.bind('focus.tabs', function () {
					addState('focus', $(this).closest('li'));
				});
				this.anchors.bind('blur.tabs', function () {
					removeState('focus', $(this).closest('li'));
				});
			}

			if (o.showOption === undefined || o.showOption === null) {
				o.showOption = {}; 
			}
			this._normalizeBlindOption(o.showOption);

			if (o.hideOption === undefined || o.hideOption === null) {
				o.hideOption = {}; 
			}
			this._normalizeBlindOption(o.hideOption);

			// Show a tab...
			showTab = ((o.showOption.blind || o.showOption.fade) && 
				o.showOption.duration > 0) ?
			function (clicked, $show) {
				$(clicked).closest('li').addClass('ui-tabs-selected ui-state-active')
					.attr('aria-selected', true);
				self._showContent();
				$show.removeClass('ui-tabs-hide').attr('aria-hidden', false);

				if (tabsAlign === 'top' || tabsAlign === 'bottom') {
					var props = { duration: o.showOption.duration };
					if (o.showOption.blind) {
						props.height = 'toggle'; 
					}
					if (o.showOption.fade) {
						props.opacity = 'toggle'; 
					}
					// avoid flicker that way
					$show.hide().removeClass('ui-tabs-hide').attr('aria-hidden', false)
					.animate(props, o.showOption.duration || 'normal', function () {
						self._resetStyle($show);
						if ($show.wijTriggerVisibility) {
							$show.wijTriggerVisibility();
						}
						self._trigger('show', null, self._ui(clicked, $show[0]));
					});
				} else {
					self._showContent();
					self._blindPanel($show, 'show');
				}
			} :
			function (clicked, $show) {
				$(clicked).closest('li').addClass('ui-tabs-selected ui-state-active')
					.attr('aria-selected', true);
				self._showContent();
				$show.removeClass('ui-tabs-hide').attr('aria-hidden', false);
				if ($show.wijTriggerVisibility) {
					$show.wijTriggerVisibility();
				}
				self._trigger('show', null, self._ui(clicked, $show[0]));
			};

			// Hide a tab, $show is optional...
			hideTab = ((o.hideOption.blind || o.hideOption.fade) && 
				o.hideOption.duration > 0) ?
			function (clicked, $hide) {
				if (tabsAlign === 'top' || tabsAlign === 'bottom') {
					var props = { duration: o.hideOption.duration };
					if (o.hideOption.blind) {
						props.height = 'toggle'; 
					}
					if (o.hideOption.fade) {
						props.opacity = 'toggle'; 
					}
					$hide.animate(props, o.hideOption.duration || 'normal', function () {
						self.lis.removeClass('ui-tabs-selected ui-state-active')
							.attr('aria-selected', false);
						$hide.addClass('ui-tabs-hide').attr('aria-hidden', true);
						self._resetStyle($hide);
						self.element.dequeue("tabs");
					});
				} else {
					self._saveLayout();
					self._blindPanel($hide, 'hide');
				}
			} :
			function (clicked, $hide, $show) {
				self.lis.removeClass('ui-tabs-selected ui-state-active')
					.attr('aria-selected', false);
				self._hideContent();
				$hide.addClass('ui-tabs-hide').attr('aria-hidden', true);
				self.element.dequeue("tabs");
			};

			// attach tab event handler, unbind to avoid duplicates from former tabifying
			if (!o.disabledState && !o.disabled) {
				this.anchors.bind(o.event + '.tabs', function () {
					var el = this,
				$li = $(this).closest('li'),
				$hide = self.panels.filter(':not(.ui-tabs-hide)'),
				$show = $(self._sanitizeSelector(this.hash), self.element);

					// If tab is already selected and not collapsible or tab disabled or
					// or is already loading or click callback returns false stop here.
					// Check if click handler returns false last so that it is not
					// executed for a disabled or loading tab!
					if (($li.hasClass('ui-tabs-selected') && !o.collapsible) ||
					$li.hasClass('ui-state-disabled') ||
					$li.hasClass('ui-state-processing') ||
					self._trigger('select', null, self._ui(this, $show[0])) === false) {
						this.blur();
						return false;
					}

					o.selected = self.anchors.index(this);

					self.abort();

					// if tab may be closed
					if (o.collapsible) {
						if ($li.hasClass('ui-tabs-selected')) {
							o.selected = -1;

							if (o.cookie) {
								self._cookie(o.selected, o.cookie);
							}

							self.element.queue("tabs", function () {
								hideTab(el, $hide);
							}).dequeue("tabs");

							this.blur();
							return false;
						}
						else if (!$hide.length) {
							if (o.cookie) {
								self._cookie(o.selected, o.cookie);
							}

							self.element.queue("tabs", function () {
								showTab(el, $show);
							});

							// TODO make passing in node possible, 
							// see also http://dev.jqueryui.com/ticket/3171
							self.load(self.anchors.index(this));

							this.blur();
							return false;
						}
					}

					if (o.cookie) {
						self._cookie(o.selected, o.cookie);
					}

					// show new tab
					if ($show.length) {
						if ($hide.length) {
							self.element.queue("tabs", function () {
								hideTab(el, $hide);
							});
						}
						self.element.queue("tabs", function () {
							showTab(el, $show);
						});

						self.load(self.anchors.index(this));
					}
					else {
						throw 'jQuery UI Tabs: Mismatching fragment identifier.';
					}

					// Prevent IE from keeping other link focussed when using
					// the back button and remove dotted border from clicked link.
					// This is controlled via CSS in modern browsers;
					// blur() removes focus from address bar in Firefox which can 
					// become a usability and annoying problem with tabs('rotate').
					if ($.browser.msie) {
						this.blur();
					}
				});
			}

			this._initScroller();

			// disable click in any case
			this.anchors.bind('click.tabs', function () {
				return false; 
			});

		},
		
		destroy: function () {
			
			this._innerDestroy();
			$.Widget.prototype.destroy.apply(this, arguments);
		},

		_innerDestroy: function () {
			var o = this.options,
				content = $('.wijmo-wijtabs-content');
			this.abort();
			this._removeScroller();
			this.element.unbind('.tabs')
			.removeClass([
				'wijmo-wijtabs',
				'ui-tabs-top',
				'ui-tabs-bottom',
				'ui-tabs-left',
				'ui-tabs-right',
				'ui-tabs',
				'ui-widget',
				'ui-widget-content',
				'ui-corner-all',
				'ui-tabs-collapsible',
				'ui-helper-clearfix'
			].join(' '))
			.removeData('tabs')
			.removeAttr('role');

			this.list.removeClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix' + 
				'ui-widget-header ui-corner-all')
			.removeAttr('role');

			this.anchors.each(function () {
				var href = $.data(this, 'href.tabs'),
					$this;
				if (href) {
					this.href = href;
				}
				$this = $(this).unbind('.tabs');
				$.each(['href', 'load', 'cache'], function (i, prefix) {
					$this.removeData(prefix + '.tabs');
				});
			});

			this.lis.unbind('.tabs').add(this.panels).each(function () {
				if ($.data(this, 'destroy.tabs')) {
					$(this).remove();
				} else {
					$(this).removeClass([
						'ui-state-default',
						'ui-corner-top',
						'ui-corner-bottom',
						'ui-corner-left',
						'ui-corner-right',
						'ui-tabs-selected',
						'ui-state-active',
						'ui-state-hover',
						'ui-state-focus',
						'ui-state-disabled',
						'ui-tabs-panel',
						'ui-widget-content',
						'ui-tabs-hide'
					].join(' ')).css({ position: '', left: '', top: '' })
					.removeAttr('role')
					.removeAttr('aria-hidden')
					.removeAttr('aria-selected')
					.removeAttr('aria-disabled');
				}
			});

			if (content.length) {
				content.replaceWith(content.contents());
			}

			if (o.cookie) {
				this._cookie(null, o.cookie);
			}

			return this;
		},

		add: function (url, label, index) {
			/// <summary>
			/// Add a new tab.
			/// Code example: $("#element").wijtabs('add', url, label, [index]); 
			/// </summary>
			/// <param name="url" type="String">A URL consisting of a fragment
			/// identifier only to create an in-page tab or a full url 
			/// (relative or absolute, no cross-domain support) to turn the new tab
			/// into an Ajax (remote) tab.</param>
			/// <param name="label" type="String">
			/// The tab label.
			/// </param>
			/// <param name="index" type="Number">
			/// Zero-based position where to insert the new tab.
			///</param>
			if (index === undefined) {
				index = this.anchors.length; // append by default
			}

			var self = this, o = this.options,
				$li = $((o.tabTemplate || self._defaults.tabTemplate)
					.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)),
				id = !url.indexOf('#') ? url.replace('#', '') : 
					this._tabId($('a', $li)[0]),
				tabsAlign = this._getAlignment(),
				panelCorner = this._getAlignment(false),
				$panel = $('#' + id),
				$content;
			$li.addClass('ui-state-default' + ' ui-corner-' + tabsAlign)
				.data('destroy.tabs', true)
				.attr('role', 'tab')
				.attr('aria-selected', false);

			// try to find an existing element before creating a new one
			if (!$panel.length) {
				$panel = $(o.panelTemplate || self._defaults.panelTemplate)
					.attr('id', id)
					.data('destroy.tabs', true)
					.attr('role', 'tabpanel');
			}
			$panel.addClass('ui-tabs-panel ui-widget-content ui-corner-' + 
				panelCorner + ' ui-tabs-hide').attr('aria-hidden', true);

			if (index >= this.lis.length) {
				$li.appendTo(this.list);
				if (this.panels.length > 0) {
					$panel.insertAfter(this.panels[this.panels.length - 1]);
				} else {
					$content = this.element.find('.wijmo-wijtabs-content');
					if ($content.length === 0) {
						$content = this.element;
					}

					//$panel.appendTo(this.list[0].parentNode);
					$panel.appendTo($content);
				}
			}
			else {
				$li.insertBefore(this.lis[index]);
				$panel.insertBefore(this.panels[index]);
			}

			o.disabledIndexes = $.map(o.disabledIndexes, function (n, i) {
				return n >= index ? ++n : n; 
			});

			this._removeScroller();
			this._tabify();

			if (this.anchors.length === 1) { // after tabify
				o.selected = 0;
				$li.addClass('ui-tabs-selected ui-state-active')
					.attr('aria-selected', true);
				$panel.removeClass('ui-tabs-hide').attr('aria-hidden', false);
				this.element.queue("tabs", function () {
					if (self.element.wijTriggerVisibility) {
						$(self.panels[0]).wijTriggerVisibility();
					}
					self._trigger('show', null, self._ui(self.anchors[0], 
						self.panels[0]));
				});

				this.load(0);
			}

			// callback
			this._trigger('add', null, this._ui(this.anchors[index], this.panels[index]));
			return this;
		},

		remove: function (index) {
			/// <summary>
			/// Remove a tab.
			/// Code example: $("#element").wijtabs('remove', 1); 
			/// </summary>
			/// <param name="index" type="Number">
			/// The zero-based index of the tab to be removed.
			/// </param>
			var o = this.options, $li = this.lis.eq(index).remove(),
				$panel = this.panels.eq(index).remove();

			// If selected tab was removed focus tab to the right or
			// in case the last tab was removed the tab to the left.
			if ($li.hasClass('ui-tabs-selected') && this.anchors.length > 1) {
				this.select(index + (index + 1 < this.anchors.length ? 1 : -1));
			}

			o.disabledIndexes = $.map($.grep(o.disabledIndexes, function (n, i) {
				return n !== index;
			}), function (n, i) {
				return n >= index ? --n : n; 
			});

			this._removeScroller();
			this._tabify();

			// callback
			this._trigger('remove', null, this._ui($li.find('a')[0], $panel[0]));
			return this;
		},

		enableTab: function (index) {
			/// <summary>
			/// Enable a disabled tab.
			/// Code example: $("#element").wijtabs('enableTab', 1); 
			/// </summary>
			/// <param name="index" type="Number">
			/// The zero-based index of the tab to be enabled.
			/// </param>
			var o = this.options;
			if ($.inArray(index, o.disabledIndexes) === -1) {
				return;
			}

			this.lis.eq(index)
				.removeClass('ui-state-disabled')
				.removeAttr('aria-disabled');
			o.disabledIndexes = $.grep(o.disabledIndexes, function (n, i) {
				return n !== index; 
			});

			// callback
			this._trigger('enable', null, this._ui(this.anchors[index], 
				this.panels[index]));
			return this;
		},

		disableTab: function (index) {
			/// <summary>
			/// Disabled a tab.
			/// Code example: $("#element").wijtabs('disableTab', 1); 
			/// </summary>
			/// <param name="index" type="Number">
			/// The zero-based index of the tab to be disabled.
			/// </param>
			var o = this.options;
			if (index !== o.selected) { // cannot disable already selected tab
				this.lis.eq(index).addClass('ui-state-disabled')
					.attr('aria-disabled', true);

				o.disabledIndexes.push(index);
				o.disabledIndexes.sort();

				// callback
				this._trigger('disable', null, this._ui(this.anchors[index], 
					this.panels[index]));
			}

			return this;
		},

		select: function (index) {
			/// <summary>
			/// Select a tab, as if it were clicked.
			/// Code example: $("#element").wijtabs('select', 1); 
			/// </summary>
			/// <param name="index" type="Number">
			/// The zero-based index of the tab to be selected or
			/// the id selector of the panel the tab is associated with.
			/// </param>
			if (typeof index === 'string') {
				index = this.anchors.index(this.anchors.filter('[href$=' + index + ']'));
			}
			else if (index === null) {
				// usage of null is deprecated, TODO remove in next release
				index = -1;
			}
			if (index === -1 && this.options.collapsible) {
				index = this.options.selected;
			}

			this.anchors.eq(index).trigger(this.options.event + '.tabs');
			return this;
		},

		load: function (index) {
			/// <summary>
			/// Reload the content of an Ajax tab programmatically. 
			/// This method always loads the tab content from the remote location,
			/// even if cache is set to true. 
			/// The second argument is the zero-based index of the tab to be reloaded.
			/// Code example: $("#element").wijtabs('load', 1); 
			/// </summary>
			/// <param name="index" type="Number">
			/// The zero-based index of the tab to be loaded
			/// </param>
			var self = this,
				o = this.options,
				a = this.anchors.eq(index)[0], 
				url = $.data(a, 'load.tabs'),
				span = $('span', a);

			this.abort();

			// not remote or from cache
			if (!url || this.element.queue("tabs").length !== 0 && 
					$.data(a, 'cache.tabs')) {
				this.element.dequeue("tabs");
				return;
			}

			// load remote from here on
			this.lis.eq(index).addClass('ui-state-processing');

			if (o.spinner || self._defaults.spinner) {
				span.data('label.tabs', span.html())
					.html(o.spinner || self._defaults.spinner);
			}

			this.xhr = $.ajax($.extend({}, o.ajaxOptions, {
				url: url,
				success: function (r, s) {
					$(self._sanitizeSelector(a.hash), self.element).html(r);

					// take care of tab labels
					self._cleanup();

					if (o.cache) { 
						// if loaded once do not load them again
						$.data(a, 'cache.tabs', true);
					}

					// callbacks
					self._trigger('load', null, self._ui(self.anchors[index], 
						self.panels[index]));
					try {
						o.ajaxOptions.success(r, s);
					}
					catch (e1) { }
				},
				error: function (xhr, s, e) {
					// take care of tab labels
					self._cleanup();

					// callbacks
					self._trigger('load', null, self._ui(self.anchors[index], 
						self.panels[index]));
					try {
						// Passing index avoid a race condition when this method is
						// called after the user has selected another tab.
						// Pass the anchor that initiated this request allows
						// loadError to manipulate the tab content panel via $(a.hash)
						o.ajaxOptions.error(xhr, s, index, a);
					}
					catch (e2) { }
				}
			}));

			// last, so that load event is fired before show...
			self.element.dequeue("tabs");

			return this;
		},

		abort: function () {
			/// <summary>
			/// Terminate all running tab ajax requests and animations.
			/// Code example: $("#element").wijtabs('abort'); 
			/// </summary>
			this.element.queue([]);
			this.panels.stop(false, true);

			// "tabs" queue must not contain more than two elements,
			// which are the callbacks for the latest clicked tab...
			this.element.queue("tabs", this.element.queue("tabs").splice(-2, 2));

			// terminate pending requests from other tabs
			if (this.xhr) {
				this.xhr.abort();
				delete this.xhr;
			}

			// take care of tab labels
			this._cleanup();
			return this;
		},

		url: function (index, url) {
			/// <summary>
			/// Change the url from which an Ajax (remote) tab will be loaded. 
			/// The specified URL will be used for subsequent loads. 
			/// Note that you can not only change the URL for an existing remote tab
			/// with this method, but also turn an in-page tab into a remote tab. 
			/// The second argument is the zero-based index of the tab of which
			/// its URL is to be updated. 
			/// The third is a URL the content of the tab is loaded from.
			/// Code example: $("#element").wijtabs('url', 1, url); 
			/// </summary>
			/// <param name="index" type="Number">
			/// The zero-based index of the tab of which its URL is to be updated.
			/// </param>
			/// <param name="url" type="String">
			/// A URL the content of the tab is loaded from.
			/// </param>
			this.anchors.eq(index).removeData('cache.tabs').data('load.tabs', url);
			return this;
		},

		length: function () {
			/// <summary>
			/// Retrieve the number of tabs of the first matched tab pane.
			/// Code example: $("#element").wijtabs('length'); 
			/// </summary>
			return this.anchors.length;
		}

	});
} (jQuery));